package com.cloud.rest;

import com.cloud.exceptions.BaseRuntimeException;
import com.cloud.exceptions.BusinessException;
import com.cloud.exceptions.ReqParamException;
import com.cloud.exceptions.RpcException;
import com.google.common.base.Joiner;
import com.netflix.client.ClientException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;
import java.util.stream.Collectors;

import static com.cloud.rest.EnumGlobalError.*;


/**
 * @ClassName: GlobalExceptionHandler
 * @Description: 全局异常捕获类
 * @Version: v1.0.0
 * @Author: yangyuwei
 * @Date: 2020/9/4
 * Modification History:
 * Date             Author      Version     Description
 * -------------------------------------------------------------
 * 2020/9/4      yangyuwei      v1.0.0      创建
 * 2020/9/4      chendong       v1.1.0      常见异常捕获
 * 2020/9/4      yangyuwei      v1.2.0      处理异常,统一错误码
 * 2021/8/13     hognbo.zhu     v2.1.0      异常改为枚举形式，统一公共错误码
 */
@RestControllerAdvice
@Slf4j
@Order(2)
@ConditionalOnMissingBean({GlobalExceptionHandler.class}) // 优先加载子类实现
public class GlobalExceptionHandler {

    @Value("${spring.application.name}")
    private String applicationName;
    @Value("${spring.profiles.active}")
    private String activeProfile;

    /**
     * 微服务客户端异常
     */
    @ExceptionHandler(ClientException.class)
    @ResponseStatus(HttpStatus.OK)
    public RestRes<Boolean> clientExceptionHandler(ClientException ex) {
        log.error(ex.getMessage(), ex);
        String message = "系统内部错误，请联系管理员!";
        if (!"prod".equals(activeProfile) && !"official".equals(activeProfile)) {
            String stackTrace = Joiner.on(",").join(ex.getStackTrace());
            String errorLocation = stackTrace.substring(0, stackTrace.indexOf(","));
            message += "\r\n错误位置：" + errorLocation;
        }
        return errorCode(RPC_ERROR, message);
    }

    /**
     * 微服务rpc调用异常
     */
    @ExceptionHandler(RpcException.class)
    @ResponseStatus(HttpStatus.OK)
    public RestRes<Boolean> rpcExceptionHandler(RpcException ex) {
        log.error(ex.getMessage(), ex);
        return errorCode(ex);
    }

    /**
     * 接口404异常
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.OK)
    public RestRes<Boolean> noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
        log.error(ex.getMessage(), ex);
        String message = "系统内部错误，请联系管理员!";
        if (!"prod".equals(activeProfile) && !"official".equals(activeProfile)) {
            message += "\r\n错误位置：" + ex.getMessage();
        }
        return errorCode(RPC_ERROR, message);
    }

    /**
     * 请求方法错误异常捕获
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.OK)
    public RestRes<Boolean> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
        log.error(ex.getMessage(), ex);
        String message = "系统内部错误，请联系管理员!";
        if (!"prod".equals(activeProfile) && !"official".equals(activeProfile)) {
            message += "\r\n错误位置：" + ex.getMessage();
        }
        return errorCode(METHOD_ERROR, message);
    }

    /**
     * @Description: 媒体类型异常捕获
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseStatus(HttpStatus.OK)
    public RestRes<Boolean> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException ex) {
        log.error(ex.getMessage(), ex);
        String message = "系统内部错误，请联系管理员!";
        if (!"prod".equals(activeProfile) && !"official".equals(activeProfile)) {
            message += "\r\n错误位置：" + ex.getMessage();
        }
        return errorCode(MEDIA_ERROR, message);
    }

    /**
     * 处理验证路径中请求实体校验失败后抛出的异常
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public RestRes<Boolean> bindExceptionHandler(BindException ex) {
        log.error(ex.getMessage(), ex);
        String message = "系统内部错误，请联系管理员!";
        if (!"prod".equals(activeProfile) && !"official".equals(activeProfile)) {
            String location = ex.getBindingResult().getAllErrors().stream()
                    .map(DefaultMessageSourceResolvable::getDefaultMessage)
                    .distinct().collect(Collectors.joining(","));
            message += "\r\n错误位置：" + location;
        }
        return errorCode(PARAM_ILLEGAL, message);
    }

    /**
     * 参数校验异常
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(HttpStatus.OK)
    public RestRes<Boolean> missingRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
        if (!"prod".equals(activeProfile) && !"official".equals(activeProfile)) {
            log.error(ex.getMessage(), ex);
        }
        String message = ex.getMessage();
        return errorCode(PARAM_ILLEGAL, message);
    }


    /**
     * @param ex description
     * @return com.joyoung.cloud.common.msg.RestRes<java.lang.Boolean>
     * @Description 请求参数校验失败
     * @author yangyuwei on 2020/9/4 17:57
     **/
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.OK)
    public RestRes<Boolean> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        if (!"prod".equals(activeProfile) && !"official".equals(activeProfile)) {
            log.error("参数验证失败：" + ex.getMessage(), ex);
        }
        String message = ex.getBindingResult().getAllErrors().stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .distinct().collect(Collectors.joining(","));
        return errorCode(PARAM_ILLEGAL, message);
    }

    /**
     * 请求参数校验失败
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.OK)
    public RestRes<Boolean> constraintViolationExceptionHandler(ConstraintViolationException ex) {
        if (!"prod".equals(activeProfile) && !"official".equals(activeProfile)) {
            log.error("参数验证失败：" + ex.getMessage(), ex);
        }
        Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
        String message = violations.stream()
                .map(ConstraintViolation::getMessage)
                .distinct().collect(Collectors.joining(","));
        return errorCode(PARAM_ILLEGAL, message);
    }

    /**
     * 请求参数校验失败
     */
    @ExceptionHandler(ReqParamException.class)
    @ResponseStatus(HttpStatus.OK)
    public RestRes<Boolean> reqParamExceptionHandler(ReqParamException ex) {
        if (!"prod".equals(activeProfile) && !"official".equals(activeProfile)) {
            log.error("参数验证失败：" + ex.getMessage(), ex);
        }
        return errorCode(ex);
    }

    /**
     * 空指针异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(NullPointerException.class)
    @ResponseStatus(HttpStatus.OK)
    public RestRes<Boolean> handleNullPointerException(NullPointerException ex) {
        log.error(ex.getMessage(), ex);
        String message = "系统内部错误，请联系管理员!";
        if (!"prod".equals(activeProfile) && !"official".equals(activeProfile)) {
            String stackTrace = Joiner.on(",").join(ex.getStackTrace());
            String errorLocation = stackTrace.substring(0, stackTrace.indexOf(","));
            message += "\r\n错误位置：" + errorLocation;
        }
        return errorCode(NULL_POINT_ERROR, message);
    }


    /**
     * @param ex description
     * @return com.joyoung.cloud.common.msg.RestRes<java.lang.Boolean>
     * @Description 业务异常
     * @author yangyuwei on 2020/9/4 17:16
     **/
    @ExceptionHandler(BusinessException.class)
    @ResponseStatus(HttpStatus.OK)
    public RestRes<Boolean> businessExceptionHandler(BusinessException ex) {
        return errorCode(ex);
    }

    /**
     * Assert业务异常处理
     *
     * @author tengyun
     * Examples:
     * Assert.notNull(null,"参数不能为空");
     **/
    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.OK)
    public RestRes<Boolean> illegalArgumentException(IllegalArgumentException ex) {
        String message = ex.getMessage();
        log.error(message, ex);
        return errorCode(BUSINESS_ERROR, message);
    }


    /**
     * 由于使用了 OkHttpTokenConfig.java 自定义HTTP客户端实现，feign会将 RpcException
     * 等异常重新包装成为 RuntimeException，所以需要定义此异常捕捉，用于捕捉重新包装的异常。
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(HttpStatus.OK)
    public RestRes<Boolean> runtimeException(RuntimeException ex) throws Throwable {
        log.error(ex.getMessage(), ex);
        Throwable cause = ex;
        while (cause.getCause() != null) {
            cause = cause.getCause();
        }
        if (cause instanceof ClientException) {
            ClientException srcEx = (ClientException) cause;
            return clientExceptionHandler(srcEx);
        } else if (cause instanceof RpcException) {
            RpcException rpcEx = (RpcException) cause;
            return rpcExceptionHandler(rpcEx);
        } else if (cause instanceof BusinessException) {
            BusinessException srcEx = (BusinessException) cause;
            return businessExceptionHandler(srcEx);
        } else {
            if ("prod".equalsIgnoreCase(activeProfile) || "official".equalsIgnoreCase(activeProfile)) {
                return errorCode(SYSTEM_ERROR);
            } else {
                return errorCode(SYSTEM_ERROR, cause.getMessage());
            }
        }
    }

    /**
     * @Description: 兜底异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public RestRes<Boolean> exceptionHandler(Exception ex) {
        String message = "系统内部错误，请联系管理员!";
        log.error(ex.getMessage(), ex);
        if (!"prod".equals(activeProfile) && !"official".equals(activeProfile)) {
            String stackTrace = Joiner.on(",").join(ex.getStackTrace());
            String errorLocation = stackTrace.substring(0, stackTrace.indexOf(","));
            message += "\r\n错误位置：" + errorLocation;
        }
        return errorCode(SYSTEM_ERROR, message);
    }

    /**
     * 统一使用枚举的异常信息编码，保证编码的统一
     *
     * @param error   异常
     * @param message 完整异常信息
     * @return
     */
    private RestRes<Boolean> errorCode(BaseEnumError error, String message) {
        return RestRes.errorData(wrapperCode(error.getCode()), message);
    }

    private RestRes<Boolean> errorCode(BaseEnumError error) {
        return RestRes.errorData(wrapperCode(error.getCode()), error.getMsg());
    }

    private <T extends BaseRuntimeException> RestRes<Boolean> errorCode(T runtimeException) {
        return RestRes.errorData(wrapperCode(runtimeException.getCode()), runtimeException.getMsg());
    }

    private String wrapperCode(String code) {
        return applicationName.toUpperCase() + "-" + code;
    }

}
